package com.project.framework.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.project.common.constant.LogConstants;
import com.project.common.constant.ResultCode;
import com.project.common.constant.UserConstants;
import com.project.common.exception.ApiException;
import com.project.common.core.template.Result;
import com.project.common.exception.user.UserPasswordNotMatchException;
import com.project.common.utils.ServletUtils;
import com.project.common.utils.sign.Md5Utils;
import com.project.common.utils.spring.SpringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.NoHandlerFoundException;
import com.project.common.constant.HttpStatus;
import com.project.common.exception.BaseException;
import com.project.common.exception.CustomException;
import com.project.common.exception.DemoModeException;
import com.project.common.utils.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 全局异常处理器
 *
 * @author project
 */

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

//    @Resource
//    private SendAlarmUtils sendAlarmUtils;
//
//    @Value("${env}")
//    private String env;


    @ExceptionHandler(value = Exception.class)
    public Result exception(Exception e) {
        printErrorLog(e, true, "拦截到的其他异常");
        return Result.error(HttpStatus.ERROR, "系统异常", e);
    }

    @ExceptionHandler(value = RuntimeException.class)
    public Result runtimeException(RuntimeException e) {
        String errorMsg = StringUtils.getErrorMsg(e, 2000);
        printErrorLog(e, true, "系统运行时异常");
        if (StringUtils.isNotEmpty(errorMsg) && errorMsg.length() < 50) {
            return Result.error(HttpStatus.ERROR, errorMsg, e);
        } else {
            return Result.error(HttpStatus.ERROR, "系统运行时异常", e);
        }
    }

/***********************************************业务异常***********************************************************/

    /**
     * 自定义异常
     */
    @ExceptionHandler(BaseException.class)
    public Result baseException(BaseException e) {
        printErrorLog(e, false);
        return Result.error(e.getMessage());
    }

    /**
     * 自定义异常
     */
    @ExceptionHandler(CustomException.class)
    public Result customException(CustomException e) {
        printErrorLog(e, false);
        if (StringUtils.isNull(e.getCode())) {
            return Result.error(e.getMessage());
        }
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 处理自定义异常
     *
     * @Author: 汪焰
     * @Date: 2020/6/14 20:10
     * version 1.0.0
     **/
    @ExceptionHandler(value = ApiException.class)
    public Result apiException(ApiException e) {
        printErrorLog(e, true);
        return Result.error(e.getCode(), e.getMsg());
    }

    /**
     * 路径不存在
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public Result noHandlerFoundException(Exception e) {
        printErrorLog(e, false);
        return Result.error(HttpStatus.NOT_FOUND, "路径不存在，请检查路径是否正确");
    }

    /**
     * 文件找不到
     */
    @ExceptionHandler(value = FileNotFoundException.class)
    public Result fileNotFoundException(FileNotFoundException e) {
        printErrorLog(e, true, "文件找不到");
        return Result.error(HttpStatus.ERROR, e.getMessage());
    }

    /***********************************************账号异常***********************************************************/

    @ExceptionHandler(AccessDeniedException.class)
    public Result handleAuthorizationException(AccessDeniedException e) {
        printErrorLog(e, false);
        return Result.error(HttpStatus.FORBIDDEN, "没有权限，请联系管理员授权");
    }

    @ExceptionHandler(AccountExpiredException.class)
    public Result handleAccountExpiredException(AccountExpiredException e) {
        printErrorLog(e, false);
        return Result.error(e.getMessage());
    }

    @ExceptionHandler(UsernameNotFoundException.class)
    public Result handleUsernameNotFoundException(UsernameNotFoundException e) {
        printErrorLog(e, false);
        return Result.error(e.getMessage());
    }

    /***********************************************参数校验不通过***********************************************************/

    /**
     * 参数校验异常 参数转换异常
     */
    @ExceptionHandler(BindException.class)
    public Result bindException(BindException e) {
        printErrorLog(e, false, "自定义验证异常");
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return Result.error(message);
    }

    /**
     * 处理 参数校验异常 Http消息转换异常
     */
    @ExceptionHandler(value = HttpMessageConversionException.class)
    public Result httpMessageNotReadableExceptionHandler(HttpMessageConversionException e) {
        printErrorLog(e, false, "请求参数解析异常");
        return Result.error(HttpStatus.UNSUPPORTED_TYPE, "请求参数解析异常:" + e.getLocalizedMessage());
    }

    /**
     * 处理 参数校验异常 处理validation
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        StringBuffer sb = new StringBuffer();
        for (ObjectError error : errors) {
            sb.append(error.getDefaultMessage()).append(" ; ");
        }
        printErrorLog(e, false, "参数检验不通过");
        return Result.error(HttpStatus.ERROR, "参数检验不通过:" + sb.toString());
    }

    /**
     * 参数校验异常
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public Result missingServletRequestParameterException(MissingServletRequestParameterException e) {
        printErrorLog(e, false, "必传参数为空");
        return Result.error(HttpStatus.ERROR, "必传参数为空:" + e.getMessage());
    }

    private void printErrorLog(Throwable e, boolean isPrintDetails, String... errMsg) {
        String head = "异常告警";
        String remarks = errMsg.length > 0 ? Arrays.asList(errMsg).stream().collect(Collectors.joining(",")).concat("\n") : "";
        String tail = StringUtils.buildMessage("异常类型:{},异常日志: {} -----, 详细错误日志:{}", e.getClass().getSimpleName(), remarks, StringUtils.getErrorMsg(e, 1000));

        log.info("截取的异常信息：{}", StringUtils.getErrorMsg(e, 2000));

        String alarmMsg = buildAlarmMsg(head, tail);

        //日志打印异常
        log.error(alarmMsg);
        if (isPrintDetails) {
            log.error("详细错误日志: ", e);
        }
        //将异常发送到企业微信群
        alarmMsg = alarmMsg.replaceAll(",'',", "，");
        alarmMsg = alarmMsg.replaceAll(",", "\n");
        alarmMsg = alarmMsg.replaceAll("，", ",");
        sendAlarm(e, alarmMsg);
    }

    @Value("${qiWeiQun.sendAlarmUrl:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=f21d141b-76c5-47ec-9d8b-1012b36bfd0f}")
    private String sendAlarmUrl;

    @Value("${spring.application.name}")
    private String appName;

    @Value("${env}")
    private String env;
    @Resource
    private TaskExecutor taskExecutor;

    private List<Class> notSendAlarm = Arrays.asList(UserPasswordNotMatchException.class
            , CustomException.class, BaseException.class
            , MissingServletRequestParameterException.class, MethodArgumentNotValidException.class
            , HttpMessageConversionException.class
    );
    private List<Integer> sendAlarmRate = Arrays.asList(
            0, 1, 2, 3, 5
            , 10, 20, 30, 40, 50
            , 100, 200, 300, 500
            , 1000, 2000, 3000, 5000
            , 10000, 20000, 30000, 50000
            , 100000, 200000, 300000, 500000
    );

    /**
     * 企业微信告警
     */
    public void sendAlarm(Throwable e, String msg) {
        if ("local".equals(env)) {
            e.printStackTrace();
            return;
        }

        if (notSendAlarm.contains(e.getClass())) {
            return;
        }
        Long increment = 0L;
        try {
            String redisKey = "sendAlarm:" + Md5Utils.hash(StringUtils.getErrorMsg(e, 2000));
            RedisTemplate redisTemplate = SpringUtils.getBean(RedisTemplate.class);
            if (ObjectUtil.isNotEmpty(redisTemplate)) {
                increment = redisTemplate.opsForValue().increment(redisKey);
                if (!sendAlarmRate.contains(increment)) {
                    //不在发送频率内 不发送
                    return;
                }
                redisTemplate.expire(redisKey, 5, TimeUnit.DAYS);
            }
        } catch (Exception eee) {
            log.error("获取redis连接异常：", e);
        }

        JSONObject params = new JSONObject()
                .fluentPut("msgtype", "text")
                .fluentPut("text", new JSONObject().fluentPut("content", "当前环境：".concat(env).concat("\n")
                        .concat("项目类型：").concat(appName).concat("\n")
                        .concat("告警次数：" + increment).concat("\n")
                        .concat("时间：").concat(DateUtil.now()).concat("\n")
                        .concat(msg)));
        if (StringUtils.isNotEmpty(sendAlarmUrl)) {
            CompletableFuture.runAsync(() -> HttpUtil.post(sendAlarmUrl, params.toJSONString()), taskExecutor);
        }
    }

    /**
     * 构建报警信息
     *
     * @return
     */
    public static String buildAlarmMsg(String head, String tail) {
        HttpServletRequest request = ServletUtils.getRequest();
        String apiRemarks = getApiRemarks();
        final String apiTags = getApiTags();
        String url = request.getRequestURI();

        // Dubbo 架构中线程变量中取出对象
//        CommonRequest commonRequest = RequestContext.get(RequestContextKey.COMMON_REQUEST, CommonRequest.class);
//        String traceId = ObjectUtils.isEmpty(commonRequest) ? StringUtils.EMPTY : commonRequest.getTraceId();

        Map<String, String[]> parameterMap = request.getParameterMap();
        String formParams = CollUtil.isNotEmpty(parameterMap) ? JSON.toJSONString(parameterMap) : "无";

        String bodyParams = "无";
        try (InputStream is = request.getInputStream()) {
            bodyParams = IoUtil.read(is, String.valueOf(StandardCharsets.UTF_8));
        } catch (Exception ex) {
        }

        String headerParams = StringUtils.buildMessage("token={}", request.getHeader(UserConstants.Authorization));

        return StringUtils.buildMessage("{},接口类型:{},接口名:{},接口地址:{},请求ID:{},header请求参数:{},表单请求参数:{},body请求参数:{},{}",
                head, apiTags, apiRemarks, url, MDC.get(LogConstants.TRACE_ID), headerParams, formParams, bodyParams, tail);
    }

    /**
     * 获取接口中文注释
     */
    private static String getApiTags() {
        try {
            HttpServletRequest request = ServletUtils.getRequest();
            RequestMappingHandlerMapping mapping = SpringUtils.getBean(RequestMappingHandlerMapping.class);
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler.getHandler() instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler();
                Api annotation = handlerMethod.getBeanType().getAnnotation(Api.class);
                if (annotation != null && annotation.tags().length > 0) {
                    return StringUtils.join(annotation.tags());
                }
            }
        } catch (Exception eee) {
            eee.printStackTrace();
        }
        return "@Api 接口无描述";
    }

    /**
     * 获取接口中文注释
     */
    private static String getApiRemarks() {
        try {
            HttpServletRequest request = ServletUtils.getRequest();
            RequestMappingHandlerMapping mapping = SpringUtils.getBean(RequestMappingHandlerMapping.class);
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler.getHandler() instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler();
                Method method = handlerMethod.getMethod();
                ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                if (apiOperation != null) {
                    return apiOperation.value();
                }
            }
        } catch (Exception eee) {
            eee.printStackTrace();
        }
        return "@ApiOperation 接口无描述";
    }

}
